Skip to content

Pass UTM params through to dashboard sign-up - #706

Open
goosewin wants to merge 4 commits into
mainfrom
feature/utm-passthrough-dashboard-bde56f
Open

goosewin wants to merge 4 commits into
mainfrom
feature/utm-passthrough-dashboard-bde56f

Conversation

@goosewin

@goosewin goosewin commented Oct 15, 2026 •

Copy link
Copy Markdown
Collaborator

Adds first-touch UTM passthrough to conceptual.js: the campaign is saved to the kernel_utm cookie and appended to dashboard.onkernel.com links on click. Launch posts link straight to docs pages, so the params were lost before sign-up. It runs under the same Do Not Track and marketing consent checks as the pixel, and declining clears the cookie.

To test, grant marketing consent, open /browsers/code-mode-webmcp?utm_source=x&utm_medium=social&utm_campaign=code-mode and click Sign up: the params are on the URL. Without params or consent, links are unchanged.

Pairs with kernel/website#343, which writes the same cookie.


Note

Medium Risk
Touches marketing attribution cookies and link rewriting under consent/DNT rules; mis-handling could leak campaign data or fail to honor opt-out, but scope is limited to docs → dashboard links.

Overview
Extends the docs Conceptual Analytics pixel (conceptual.js) so first-touch UTM params survive the hop from docs to dashboard sign-up, using the same kernel_utm cookie contract as kernel/website.

On landing, UTMs from the query string are stored (first touch wins, 30-day cookie on .kernel.sh). On click/auxclick, links to dashboard.onkernel.com get those params appended unless the URL already has utm_* keys; script-added params are marked with data-utm-passthrough so consent withdrawal can strip only automated decoration. This runs alongside the existing ca_device_id handoff in one handOffOnNavigation path.

Do Not Track, declined marketing consent, and consent withdrawal now also clear the UTM cookie and undo device-id and UTM rewrites in the DOM (withdraw()), matching the pixel’s privacy rules.

Reviewed by Cursor Bugbot for commit 3ca980c. Bugbot is set up for automated code reviews on this repo. Configure here.

@mintlify

mintlify Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Kernel 🟢 Ready View Preview Oct 21, 2026, 4:40 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@mintlify

mintlify Bot commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

Preview deployment for your docs. Learn more about Mintlify Previews.

Project Status Preview Updated
Kernel 🟡 Building – Oct 15, 2026, 6:02 AM

💡 Tip: Enable Automations to automatically generate PRs for you.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes using default effort and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want higher recall? High effort reviews run extra passes and find more bugs. A team admin can switch effort levels in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 577769f. Configure here.

Comment thread utm.js Outdated

@masnwilliams masnwilliams left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

requesting changes. the passthrough itself is small and the behavior matches the website side for keys, first touch, hand-tagged links and the dashboard host. the problem is privacy: kernel_utm gets written and used with no Do Not Track or consent check.

before merge

  1. check DNT and marketing consent before writing or using the cookie. privacy.mdx:85 says attribution cookies don't load when DNT is set, and the website side writes kernel_utm only with marketing consent. right now a visitor who declined on www.kernel.sh, or has DNT on, gets the cookie as soon as they open a docs page. the easiest fix is to move this into conceptual.js: its doNotTrackEnabled() / storedConsent() / jurisdiction-lookup checks already cover the same visitors, and its click handler already rewrites dashboard links. that also gets rid of the second capture-phase click listener, and ca_device_id picks up the auxclick handling this PR adds. if you'd rather keep utm.js separate, it needs the same checks either way.
  2. delete the cookie when consent is denied. if stored c15t consent is present and marketing isn't granted (storedConsent() === false), write kernel_utm with Max-Age=0 and the same Path/Domain attributes. do it at page load and when consent is withdrawn mid-session (undecorateLinks), so a cookie written before the decline doesn't stick around. kernel/website#343 is adding the same rule.
  3. store the value as a query string, not JSON. store URLSearchParams.toString() as is, with no extra encodeURIComponent, and read it with the same function that parses the landing URL: filter to the 5 keys, trim, skip empty values. that removes the JSON.parse / object-shape checks in readStored, and both repos parse the cookie the same way. both PRs have to make this change at the same time: if one side encodes and the other doesn't, the params are dropped silently. this also adds the .trim() the website side already does.

if you fold it into conceptual.js: read location.search at the top of the script, before the consent check. when there's no stored decision, start() waits on the jurisdiction fetch, and a client-side navigation during that wait loses the landing params.

non-blocking

  • mark decorated links (the website side uses data-utm-passthrough) so withdrawal removes only our params and leaves hand-tagged utm_* alone.
  • consider deleting the cookie under DNT too, not just skipping it, so one written before DNT was turned on doesn't stay.
  • a comment pointing at the website's src/lib/utm.ts as the other half of the cookie format, so the two sides stay in sync.

This branch was successfully deployed

1 active deployment
staging — 3ca980cd Deployed Oct 21, 2026 by mintlify[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants